package cn.edu.scut.hsrc.cluster.semantic;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import cn.edu.scut.hsrc.bean.CandidateWord;
import cn.edu.scut.hsrc.bean.Doc;
import cn.edu.scut.hsrc.bean.Pair;
import cn.edu.scut.hsrc.bean.RefWord;
import cn.edu.scut.hsrc.bean.Segment;
import cn.edu.scut.hsrc.bean.SeqDoc;
import cn.edu.scut.hsrc.bean.SeqWord;
import cn.edu.scut.hsrc.bean.Word;
import cn.edu.scut.hsrc.bean.Word.RelationToQuery;
import cn.edu.scut.hsrc.bean.WordSet;
import cn.edu.scut.hsrc.bean.WordSet.TS;
import cn.edu.scut.hsrc.bean.WordType.Type;
import cn.edu.scut.hsrc.util.SegUtil;
import cn.edu.scut.hsrc.util.StringUtil;
import cn.edu.scut.suggestion.DiverseSuggest;

/**
 * 预处理类
 * <br>主要功能：
 * <br>1、对预处理后的数据进行关联挖掘，建立词-文档矩阵和词的反引用关系；
 * <br>2、建立文档相似度矩阵。
 * @file Relation.java
 * @author feiyu
 *
 */

public class Relation {
	
	private final String query;						//查询词
	private final Doc[] docs;						//待聚类的文档
	private final List<Word> segedQuery;			//预处理之后的查询词
	
	private int[][] tdMatrix;						//词-文档矩阵
	private int[][] docSimMatrix;					//文档相似度矩阵
	private SeqDoc [] seqDocs;						//文档的有序词描述集合
	private WordSet [] docWords;					//文档包含词的集合（只包查询词、含动、名词）
	private WordSet allWords;						//所有文档出现的词（只包查询词、含动、名词）
	private List<CandidateWord> candidateWords;		//候选词集合
	
	
	/**
	 * 
	 * 构造函数
	 * @param query			查询词
	 * @param docs			待聚类的文档
	 * @param segedQuery	预处理后的查询词
	 */
	public Relation(String query,Doc[] docs,List<Word> segedQuery) {
		// TODO Auto-generated constructor stub
		this.query = query;
		this.docs = docs;
		this.segedQuery = segedQuery;
		int size = this.docs.length;
		this.seqDocs = new SeqDoc[size];
		for(int i=0;i<size;i++)
			this.seqDocs[i] = new SeqDoc(this.docs[i].getTrunkedTitle().size(),this.docs[i].getTrunkedSummaray().size());
		this.docWords = new WordSet[size];
		for(int i=0;i<size;i++)
		{
			this.docWords[i] = new WordSet();
		}
		this.allWords = new WordSet();
		this.candidateWords = new ArrayList<CandidateWord>();
		this.docSimMatrix = new int[size][size];
	}
	
	/**
	 * 分析文档之间的关系，建立词-文档矩阵，为下一步发觉高频短语做准备
	 */
	public void doRelation()
	{
		//分词所有文档
		analysisAllData();
		//收集所有的词，并按照它们的权重进行排序，分配词文档矩阵内存
		int wordNum = collectAllWords();
		System.out.println("wordNum: "+wordNum);
		
		this.tdMatrix = new int[wordNum][this.docs.length];
		//填充词-文档矩阵，里面的值是: 词在文档中的权重
		genTDMatrix();
		//产生初步的候选词序列
		genCandidateWords();
		//产生文档相似度矩阵
		genDocSimMatrix();
		
		//分词所有文档
		/*long time1 = System.currentTimeMillis();
		analysisAllData();
		time1 = System.currentTimeMillis() - time1;
		
		//收集所有的词，并按照它们的权重进行排序，分配词文档矩阵内存
		long time2 = System.currentTimeMillis();
		int wordNum = collectAllWords();
		time2 = System.currentTimeMillis() - time2;
		this.tdMatrix = new int[wordNum][this.docs.length];
		
		//填充词-文档矩阵，里面的值是: 词在文档中的权重
		long time3 = System.currentTimeMillis();
		genTDMatrix();
		time3 = System.currentTimeMillis() - time3;
		
		//产生初步的候选词序列
		long time4 = System.currentTimeMillis();
		genCandidateWords();
		time4 = System.currentTimeMillis() - time4;
		
		//产生文档相似度矩阵
		long time5 = System.currentTimeMillis();
		genDocSimMatrix();
		time5 = System.currentTimeMillis() - time5;
		System.out.println("关联分析消耗时间比例  ："+time1+":"+time2+":"+time3+":"+time4+":"+time5);
		*/
	}
	
	/**
	 * 对所有的原始文档的数据进行分析，记录相应的信息到相应的变量中
	 */
	private void analysisAllData()
	{
		int docNum = this.docs.length;
		List<String> querys = new ArrayList<String>();
		for (Word word : this.segedQuery) {
			querys.add(word.getText());
		}
		//对所有文档一一进行遍历，记录相应信息
		for(int docIndex=0;docIndex<docNum;docIndex++)
		{
			Doc doc = this.docs[docIndex];
			//这些title和summary只保留了和查询词相关的那部分
			List<Segment> title = doc.getTrunkedTitle();
		//	System.out.println("title:" +title);
			
			List<Segment> summary = doc.getTrunkedSummaray();
			//System.out.println("summary:" +summary);
			//分析title
			analysis(docIndex, title, TS.TITLE, querys);
			//分析summary
			analysis(docIndex, summary, TS.SUMMARY, querys);
		}
	}
	
	/**
	 * 对所有文档中出现的统计它们的权重，然后排序，返回词的总个数
	 * @return 返回词的总个数
	 */
	private int collectAllWords()
	{
		int DOC_NUM = this.docWords.length;
		for(int i=0;i<DOC_NUM;i++)
		{
			int count = this.docWords[i].count();	//获取第i个文档词的总个数
			for(int j=0;j<count;j++)
			{
				Pair<RefWord, Integer> word = this.docWords[i].getWordAndWeight(j);
				//此时word里面的RefWord里面的反引用对应的词在第i个文档中的<段号，段内偏移>的引用，
				//但在全局词(allWords)中，反引用的对象变成了<文档号,段号>，需要特别注意
				Map<Integer, List<Integer>> refs = word.getFirst().getRefs();	//获取这个词在文档出现的反引用
				List<Integer> segments = new ArrayList<Integer>();
				Iterator<Map.Entry<Integer, List<Integer>>> iterator = refs.entrySet().iterator();
				while (iterator.hasNext()) {
					segments.add(iterator.next().getKey());
				}
				RefWord refWord = new RefWord(word.getFirst(), i,segments);
				this.allWords.addWord(refWord,word.getSecond());
			}
		}
		this.allWords.sort();
		return this.allWords.count();
	}
	
	/**
	 * 填充词-文档矩阵，里面的值是：词在文档中的权重
	 */
	private void genTDMatrix()
	{
		int DOC_NUM = this.docs.length;				//文档的长度
		//填充词-文档矩阵
		for(int docIndex=0;docIndex<DOC_NUM;docIndex++)
		{
			WordSet docWords = this.docWords[docIndex];
			int tmpWordCount = docWords.count();
			for(int i=0;i<tmpWordCount;i++)
			{
				int index = this.allWords.indexOf(docWords.getWord(i));
				this.tdMatrix[index][docIndex] = docWords.getWeight(i);
			}
		}
	}
	
	/**
	 * 对于每个词，计算出现它的文档个数，保留大于给定阈值的。产生初步的候选词序列
	 * 
	 */
	/*
	private void genCandidateWords()
	{
		//对于每个词，计算出现它的文档个数
		int ALL_WORD_NUM = this.allWords.count();
		int DOC_NUM = this.docs.length;
		List<Pair<String, Integer>> countN = new ArrayList<Pair<String,Integer>>();
		for(int i=0 ;i<ALL_WORD_NUM;i++)
		{
			RefWord word = this.allWords.getWord(i);
			int count = 0;
			//这里应该可以直接扫描tdMatrix就可以 ，但是效果怎么样呢？可以试试
			for(int j=0;j<DOC_NUM;j++)
			{
				if(this.docWords[j].indexOf(word)>=0)
					count++;
			}
			countN.add(new Pair<String, Integer>(word.getText(),count));
			if(count >= 3 && word.length() >= 2 && StringUtil.isAllChinese(word.getText())){
				this.candidateWords.add(new CandidateWord(word, i,candidateWords.size()));
				//System.out.println(word.getText());
			}
		}
	}
	*/
	private void genCandidateWords(){
		DiverseSuggest ds;
		try {
			ds = new DiverseSuggest();
			this.candidateWords=ds.getCanWordComunitied2(query, this.allWords);
		} catch (RemoteException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	
	/**
	 * 产生文档相似度矩阵
	 */
	private void genDocSimMatrix()
	{
		//通过计算包含词的共现性，来填充文档相似度矩阵，但这里不计算查询词
		int DOC_NUM = this.docs.length;
		int ALL_WORD_NUM = this.allWords.count();
		for(int i=0;i<DOC_NUM;i++)
		{
			for(int j=i+1;j<DOC_NUM;j++)
			{
				int similarity = 0;
				for(int k = 0;k<ALL_WORD_NUM;k++)
				{
					//这里实现有点不同
					if(this.allWords.getWord(k).getRelationToQuery() == RelationToQuery.EQUAL)
						continue;
					int numi = this.tdMatrix[k][i];
					int numj = this.tdMatrix[k][j];
					numi = (numi>3)?3:numi;
					numj = (numj>3)?3:numj;
					int num3 = numi*numj;
					num3 = (num3>4)?4:num3;
					similarity += num3;       
				}
				similarity = (similarity>10)?10:similarity;
				this.docSimMatrix[i][j] = similarity;
				this.docSimMatrix[j][i] = similarity;
			}
		}
	}
	
	
	
	/**
	 * 分析文档的标题或摘要
	 * @param docIndex 	   文档下标编号
	 * @param segments   待分析的字符串对应的段集合（这些分段和查询词有强关联关系）
	 * @param where		   字符串的来源（标题，摘要）
	 * @param querys	    查询词分词后的集合（这个参数主要是为了加快运行速度用的）
	 */
	private void analysis(int docIndex,List<Segment> segments,TS where,List<String> querys)
	{
		//把待分析的字符串用查询词分割开来，一段一段进行分析<词，是否是查询词标记>
		int segmentNum = segments.size();
		for(int i=0;i<segmentNum;i++)
		{
			//把该分段用查询词分割开来
			List<Pair<String, Boolean>> terms = StringUtil.splitByPatterns(segments.get(i).getText(), querys, true);
			int size = terms.size();
			int offset = 0;
			for(int j=0;j<size;j++)
			{
				Pair<String, Boolean> term = terms.get(j);
				if(term.getSecond())//如果是查询词，就按照查询词的方式处理
				{
					int realSegment = this.seqDocs[docIndex].addWord(new SeqWord(term.getFirst(), Type.NOUN,RelationToQuery.EQUAL, offset),where,i);
					this.docWords[docIndex].addWord(new RefWord(term.getFirst(), Type.NOUN,RelationToQuery.EQUAL,realSegment,offset), where);
					offset++;
				}else {//如果是普通的词的分段，就用分词器进行分词，然后一个一个词依次处理
					List<Pair<String, Type>> words = SegUtil.seg(term.getFirst());
					int tmpSize = words.size();
					for(int t=0;t<tmpSize;t++)
					{
						Pair<String, Type> pair = words.get(t);
						RelationToQuery relationToQuery = calRelationWithQuery(pair.getFirst());
						int realSegment =  this.seqDocs[docIndex].addWord(new SeqWord(pair.getFirst(), pair.getSecond(), relationToQuery,offset),where,i);
						if((pair.getSecond() == Type.NOUN || pair.getSecond() == Type.VERB) && pair.getFirst().length()>1)//如果是名词或动词，而且长度大于1就加入文档词库中
						{
							this.docWords[docIndex].addWord(new RefWord(pair.getFirst(),pair.getSecond(),relationToQuery,realSegment,offset), where);
						}
						offset++;
					}
				}
			}
		}
	}
	
	/**
	 * 计算str和query之间的互包含关系，但这个关系是按照字来计算的，
	 * 这个函数有问题
	 * @param string
	 * @return 如果两者相等，返回RelationToQuery.EQUAL;
	 * 		        如果query包含string，返回RelationToQuery.RINCLUDE;
	 *         如果string包含query，返回RelationToQuery.INCLUDE;
	 *         如果上述情况都不是，返回RelationToQuery.NOTHING
	 */
	private RelationToQuery calRelationWithQuery(String string)
	{
		if(this.query.equals(string))
			return RelationToQuery.EQUAL;
		if(this.query.indexOf(string)>=0)
			return RelationToQuery.RINCLUDE;
		if(string.indexOf(this.query) >= 0)
		{
			if(isStrongLink(this.query, string))
				return RelationToQuery.EQUAL;
			return RelationToQuery.INCLUDE;
		}else {//判断字符串string和查询词之间是否存在一方包含另外一方，如果包含就返回2
			//这里代码不敢恭维啊
			String max  = this.query;
			String min = string;
			if(this.query.length() < string.length())
			{
				max = string;
				min = this.query;
			}
			int minLength = min.length();
			int commNum = 0;//计算它们相同的字符个数
			for(int i=0;i<minLength;i++)
			{
				if(max.indexOf(min.substring(i,i+1)) >= 0)
					commNum++;
			}
			if(commNum == minLength)
				return RelationToQuery.EQUAL;
		}
		return RelationToQuery.NOTHING;
	}
	
	/**
	 * 统计字符串str1和str2的协同出现概率，以此判断它们是否具有强关联性
	 * 第一个str1占有主导地位，一般其值为查询词(原因呢？)
	 * @param str1
	 * @param str2
	 * @return
	 */
	private boolean isStrongLink(String str1,String str2)
	{
		int num1 = 0,num2 = 0;
		int size = this.docs.length;
		for(int i=0;i<size;i++)
		{
			String title = this.docs[i].getTitle();
			String summary = this.docs[i].getSummary();
			if(title.indexOf(str1) >= 0)
			{
				num1++;
				if(title.indexOf(str2) >=0 || summary.indexOf(str2) >= 0)
					num2++;
			}else if(summary.indexOf(str1) >= 0){
				num1++;
				if(summary.indexOf(str2) >= 0)//为什么和前面不一样呢？
					num2++;
			}
		}
		if(num1 != 0 && (double)num2/num1 >= 0.7)//阈值有根据吗？
			return true;
		return false;
	}

	/**
	 * 返回文档的向量表示数组
	 * @return
	 */
	public SeqDoc[] getSeqDocs() {
		return this.seqDocs;
	}

	/**
	 * 返回文档包含的词集数组
	 * @return
	 */
	public WordSet[] getDocWords() {
		return this.docWords;
	}
	
	/**
	 * 
	 * @return
	 */
	public int[][] getDocSimMatrix() {
		return this.docSimMatrix;
	}

	/**
	 * 返回所有文档包含的词集
	 * @return
	 */
	public WordSet getAllWords() {
		return this.allWords;
	}

	/**
	 * 返回候选词集合
	 * @return
	 */
	public List<CandidateWord> getCandidateWords() {
		return this.candidateWords;
	}
}